﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DDD {


    /// <summary>
    /// 頂点バッファー
    /// </summary>
    /// <remarks>
    /// 頂点バッファーは複数の頂点データ(<see cref="VertexArray"/>)を保持するコンテナクラスです。
    /// 原則としてエンジン側では頂点データの意味(セマンティクス)は気にしません。
    /// ただし座標位置(Positions)と法線(Normals)の２つだけはエンジン側でも使用するため
    /// ユーザーが教えてやる必要があります。
    /// (メモ)UniformArrayに併せてAddAttribute()ではなくAdd()にすべきか？
    /// </remarks>
    public class VertexBuffer : Object, IEnumerable<AttributeVariable> {

        #region Field
        List<AttributeVariable> attrs;
        #endregion

        #region Constructor
        /// <summary>
        /// コンストラクター
        /// </summary>
        public VertexBuffer () {
            this.attrs = new List<AttributeVariable> ();
        }
        #endregion

        #region Property
        /// <summary>
        /// 現在セットされている頂点データの総数
        /// </summary>
        public int VertexArrayCount {
            get { return attrs.Count; }
        }

        /// <summary>
        /// 全てのAttribute変数を列挙する列挙子
        /// </summary>
        public IEnumerable<AttributeVariable> Attributes {
            get { return attrs; }
        }

        /// <summary>
        /// 全ての<see cref="VertexArray"/>を列挙する列挙子
        /// </summary>
        public IEnumerable<VertexArray> VertexArrays {
            get { return attrs.Select (x => x.VertexArray); }
        }

        /// <summary>
        /// この頂点バッファーで使用する頂点の数
        /// </summary>
        /// <remarks>
        /// この頂点バッファーで使用する頂点アレイの頂点数はすべて同一である必要があります。
        /// まだ頂点アレイが登録されていない場合0が返ります。
        /// </remarks>
        public int VertexCount {
            get {
                if (attrs.Count > 0) {
                    return attrs[0].VertexArray.VertexCount;
                }
                return 0;
            }
        }

        /// <summary>
        /// このオブジェクトが参照する全DDDオブジェクトを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// 参照にnullは含まれません。同一のオブジェクトが複数出現する可能性があります。
        /// </remarks>
        public override IEnumerable<Object> References {
            get {
                var objs = base.References.ToList ();
                objs.AddRange (attrs.Select (x => x.VertexArray));
                objs.RemoveAll (x => x == null);
                return objs;
            }
        }
        #endregion

        #region Method

        /// <summary>
        /// 頂点バッファーに頂点データ(<see cref="VertexArray"/>)を追加します
        /// </summary>
        /// <remarks>
        /// セットした頂点データはシェーダーから<paramref name="name"/>という名前のAttribute変数として
        /// アクセス可能です。
        /// すでに同名のAttribute変数がセットされていた場合、古い方を削除して新しいもので置き換えます。
        /// ある頂点バッファーにセットされる頂点データの頂点数は全て同じである必要があります。
        /// </remarks>
        /// <param name="name">Attribute変数として参照するときの名前</param>
        /// <param name="varry">頂点データ(<see cref="VertexArray"/>)</param>
        public void AddVertexArray (string name, VertexArray varry) {
            if (varry == null) {
                throw new ArgumentNullException ("VertexArray is null");
            }
            if (name == null || name == "") {
                throw new ArgumentNullException ("Name is null or empty");
            }
            if (VertexCount > 0 && VertexCount != varry.VertexCount) {
                throw new ArgumentException ("All VertexCount must be same");
            }

            this.attrs.RemoveAll (x => x.Name == name);
            this.attrs.Add (new AttributeVariable (name, varry));
        }



        /// <summary>
        /// 頂点データを削除します
        /// </summary>
        /// <remarks>
        /// 指定の頂点データを削除します。そのような頂点データが登録されていなかった場合エラーにはせずに単に無視します。
        /// (メモ)ここ名前で削除できるようにすべきか非常に悩んでいる。
        /// </remarks>
        /// <param name="varry">頂点データ</param>
        /// <returns>削除したらtrue, そうでなければfalse</returns>
        public bool RemoveVertexArray (VertexArray varry) {
            if (varry == null) {
                throw new ArgumentNullException ("VertexArray is null");
            }
            return this.attrs.RemoveAll (x => x.VertexArray == varry) > 0;
        }

        /// <summary>
        /// 指定の名前の<see cref="VertexArray"/>変数を取得するインデクサー
        /// </summary>
        /// <param name="name">Attbibute変数名</param>
        /// <returns></returns>
        public VertexArray this[string name] {
            get { return GetVertexArray (name); }
        }

        /// <summary>
        /// 指定の名前で登録されたAttribute変数を取得します
        /// </summary>
        /// <remarks>
        /// 該当するAttribute変数が登録されていない場合は例外が返ります。
        /// </remarks>
        /// <param name="name">Attribute変数名</param>
        /// <returns>Attribute変数</returns>
        public AttributeVariable GetAttribute (string name) {
            if (name == null || name == "") {
                throw new ArgumentNullException ("Name is null or empty");
            }
            return attrs.First (x => x.Name == name);
        }

        /// <summary>
        /// 指定の名前で登録された<see cref="VertexArray"/>を取得します
        /// </summary>
        /// <remarks>
        /// 該当するAttribute変数が登録されていない場合は例外が返ります。
        /// </remarks>
        /// <param name="name">Attribute変数名</param>
        /// <returns></returns>
        public VertexArray GetVertexArray (string name) {
            if (name == null || name == "") {
                throw new ArgumentNullException ("Name is null or empty");
            }
            return (from x in attrs
                   where x.Name == name
                   select x.VertexArray).First();
        }

        /// <summary>
        /// 列挙子
        /// </summary>
        /// <returns></returns>
        IEnumerator<AttributeVariable> IEnumerable<AttributeVariable>.GetEnumerator () {
            return ((IEnumerable<AttributeVariable>)attrs).GetEnumerator ();
        }

        /// <summary>
        /// 列挙子
        /// </summary>
        /// <returns></returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () {
            return attrs.GetEnumerator ();
        }

        /// <summary>
        /// 現在のオブジェクトの状態を表す文字列を返します。
        /// </summary>
        /// <returns>現在のオブジェクトを説明する文字列</returns>
        public override string ToString () {
            return string.Format ("VertexBuffer: Attributes={0}", VertexArrayCount);
        }
        #endregion


    }
}
